{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import h5py\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "%matplotlib inline\n",
    "plt.rcParams['figure.figsize']=(5.0, 4.0) \n",
    "plt.rcParams['image.interpolation']='nearest'\n",
    "plt.rcParams['image.cmap']='gray'\n",
    "\n",
    "%load_ext autoreload\n",
    "%autoreload 2\n",
    "\n",
    "np.random.seed(1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# zero_pad 填充\n",
    "def zero_pad(X, pad):\n",
    "    \"\"\"\n",
    "    把数据集的height和width全部使用0来填充pad个单位\n",
    "    \"\"\"\n",
    "    X_pad = np.pad(X, (\n",
    "        (0,0), # 样本数，不填充\n",
    "        (pad,pad), #高度，上x下y\n",
    "        (pad,pad),#宽度\n",
    "        (0,0) #通道，不填充\n",
    "        ),'constant', constant_values=0)#使用一个不变的数填充\n",
    "    \n",
    "    return X_pad"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# test\n",
    "np.random.seed(1)\n",
    "x = np.random.randn(4,3,3,2)\n",
    "x_pad = zero_pad(x, 2)\n",
    "print(\"x,shape:\", x.shape)\n",
    "print(\"x_pad.shape:\", x_pad.shape)\n",
    "print(\"x[1,1]=\",x[1,1])\n",
    "print(\"x_pad[1,1]=\",x_pad[1,1])\n",
    "\n",
    "fig,axarr = plt.subplots(1,2)\n",
    "axarr[0].set_title('x')\n",
    "axarr[0].imshow(x[0,:,:,0])\n",
    "axarr[1].set_title(x_pad)\n",
    "axarr[1].imshow(x_pad[0,:,:,0])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#conv single step\n",
    "def conv_single_step(a_slice_prev, W, b):\n",
    "    \"\"\"\n",
    "    对上一层的输出的一片应用一个过滤器\n",
    "    \n",
    "    a_slice_prev 浅层的输出的一片，大小和filter相同\n",
    "    W (filter_size,filter_size,上一层的通道数)\n",
    "    b （1,1,1）\n",
    "    \n",
    "    Z 三维矩阵所有元素相加的和\n",
    "    \"\"\"\n",
    "    s = np.multiply(a_slice_prev,W)+b\n",
    "    Z = np.sum(s)\n",
    "    return Z"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#test\n",
    "np.random.seed(1)\n",
    "a_slice_prev = np.random.randn(4,4,3)\n",
    "W = np.random.randn(4,4,3)\n",
    "b = np.random.randn(1,1,1)\n",
    "Z = conv_single_step(a_slice_prev, W, b)\n",
    "print(\"Z=\",Z)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# GRADED FUNCTION: conv_forward\n",
    "\n",
    "def conv_forward(A_prev, W, b, hparameters):\n",
    "    \"\"\"\n",
    "   实现卷积函数的前向传播\n",
    "\n",
    "    参数：\n",
    "        A_prev - 上一层的激活输出矩阵，维度为(m, n_H_prev, n_W_prev, n_C_prev)，（样本数量，上一层图像的高度，上一层图像的宽度，上一层过滤器数量）\n",
    "        W - 权重矩阵，维度为(f, f, n_C_prev, n_C)，（过滤器大小，过滤器大小，上一层的过滤器数量，这一层的过滤器数量）\n",
    "        b - 偏置矩阵，维度为(1, 1, 1, n_C)，（1,1,1,这一层的过滤器数量）\n",
    "        hparameters - 包含了\"stride\"与 \"pad\"的超参数字典。\n",
    "\n",
    "    返回：\n",
    "        Z - 卷积输出，维度为(m, n_H, n_W, n_C)，（样本数，图像的高度，图像的宽度，过滤器数量）\n",
    "        cache - 缓存了一些反向传播函数conv_backward()需要的一些数据\n",
    "\n",
    "    \"\"\"\n",
    "    \n",
    "    #获取来自上一层数据的基本信息\n",
    "    (m , n_H_prev , n_W_prev , n_C_prev) = A_prev.shape\n",
    "\n",
    "    #获取权重矩阵的基本信息\n",
    "    ( f , f ,n_C_prev , n_C ) = W.shape\n",
    "\n",
    "    #获取超参数hparameters的值\n",
    "    stride = hparameters[\"stride\"]\n",
    "    pad = hparameters[\"pad\"]\n",
    "\n",
    "    #计算卷积后的图像的宽度高度，参考上面的公式，使用int()来进行板除\n",
    "    n_H = int(( n_H_prev - f + 2 * pad )/ stride) + 1\n",
    "    n_W = int(( n_W_prev - f + 2 * pad )/ stride) + 1\n",
    "\n",
    "    #使用0来初始化卷积输出Z\n",
    "    Z = np.zeros((m,n_H,n_W,n_C))\n",
    "\n",
    "    #通过A_prev创建填充过了的A_prev_pad\n",
    "    A_prev_pad = zero_pad(A_prev,pad)\n",
    "\n",
    "    for i in range(m):                              #遍历样本\n",
    "        a_prev_pad = A_prev_pad[i]                  #选择第i个样本的扩充后的激活矩阵\n",
    "        for h in range(n_H):                        #在输出的垂直轴上循环\n",
    "            for w in range(n_W):                    #在输出的水平轴上循环\n",
    "                for c in range(n_C):                #循环遍历输出的通道\n",
    "                    #定位当前的切片位置\n",
    "                    vert_start = h * stride         #竖向，开始的位置\n",
    "                    vert_end = vert_start + f       #竖向，结束的位置\n",
    "                    horiz_start = w * stride        #横向，开始的位置\n",
    "                    horiz_end = horiz_start + f     #横向，结束的位置\n",
    "                    #切片位置定位好了我们就把它取出来,需要注意的是我们是“穿透”取出来的，\n",
    "                    #自行脑补一下吸管插入一层层的橡皮泥就明白了\n",
    "                    a_slice_prev = a_prev_pad[vert_start:vert_end,horiz_start:horiz_end,:]\n",
    "                    #执行单步卷积\n",
    "                    Z[i,h,w,c] = conv_single_step(a_slice_prev,W[: ,: ,: ,c],b[0,0,0,c])\n",
    "\n",
    "    #数据处理完毕，验证数据格式是否正确\n",
    "    assert(Z.shape == (m , n_H , n_W , n_C ))\n",
    "\n",
    "    #存储一些缓存值，以便于反向传播使用\n",
    "    cache = (A_prev,W,b,hparameters)\n",
    "\n",
    "    return (Z , cache)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# test\n",
    "np.random.seed(1)\n",
    "\n",
    "A_prev = np.random.randn(10,4,4,3)\n",
    "W = np.random.randn(2,2,3,8)\n",
    "b = np.random.randn(1,1,1,8)\n",
    "\n",
    "hparameters = {\"pad\" : 2, \"stride\": 1}\n",
    "\n",
    "Z , cache_conv = conv_forward(A_prev,W,b,hparameters)\n",
    "\n",
    "print(\"np.mean(Z) = \", np.mean(Z))\n",
    "print(\"cache_conv[0][1][2][3] =\", cache_conv[0][1][2][3])\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def pool_forward(A_prev,hparameters,mode=\"max\"):\n",
    "    \"\"\"\n",
    "    实现池化层的前向传播\n",
    "\n",
    "    参数：\n",
    "        A_prev - 输入数据，维度为(m, n_H_prev, n_W_prev, n_C_prev)\n",
    "        hparameters - 包含了 \"f\" 和 \"stride\"的超参数字典\n",
    "        mode - 模式选择【\"max\" | \"average\"】\n",
    "\n",
    "    返回：\n",
    "        A - 池化层的输出，维度为 (m, n_H, n_W, n_C)\n",
    "        cache - 存储了一些反向传播需要用到的值，包含了输入和超参数的字典。\n",
    "    \"\"\"\n",
    "\n",
    "    #获取输入数据的基本信息\n",
    "    (m , n_H_prev , n_W_prev , n_C_prev) = A_prev.shape\n",
    "\n",
    "    #获取超参数的信息\n",
    "    f = hparameters[\"f\"]\n",
    "    stride = hparameters[\"stride\"]\n",
    "\n",
    "    #计算输出维度\n",
    "    n_H = int((n_H_prev - f) / stride ) + 1\n",
    "    n_W = int((n_W_prev - f) / stride ) + 1\n",
    "    n_C = n_C_prev\n",
    "\n",
    "    #初始化输出矩阵\n",
    "    A = np.zeros((m , n_H , n_W , n_C))\n",
    "\n",
    "    for i in range(m):                              #遍历样本\n",
    "        for h in range(n_H):                        #在输出的垂直轴上循环\n",
    "            for w in range(n_W):                    #在输出的水平轴上循环\n",
    "                for c in range(n_C):                #循环遍历输出的通道\n",
    "                    #定位当前的切片位置\n",
    "                    vert_start = h * stride         #竖向，开始的位置\n",
    "                    vert_end = vert_start + f       #竖向，结束的位置\n",
    "                    horiz_start = w * stride        #横向，开始的位置\n",
    "                    horiz_end = horiz_start + f     #横向，结束的位置\n",
    "                    #定位完毕，开始切割\n",
    "                    a_slice_prev = A_prev[i,vert_start:vert_end,horiz_start:horiz_end,c]\n",
    "\n",
    "                    #对切片进行池化操作\n",
    "                    if mode == \"max\":\n",
    "                        A[ i , h , w , c ] = np.max(a_slice_prev)\n",
    "                    elif mode == \"average\":\n",
    "                        A[ i , h , w , c ] = np.mean(a_slice_prev)\n",
    "\n",
    "    #池化完毕，校验数据格式\n",
    "    assert(A.shape == (m , n_H , n_W , n_C))\n",
    "\n",
    "    #校验完毕，开始存储用于反向传播的值\n",
    "    cache = (A_prev,hparameters)\n",
    "\n",
    "    return A,cache\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#test\n",
    "np.random.seed(1)\n",
    "A_prev = np.random.randn(2, 4, 4, 3)\n",
    "hparameters = {\"stride\" : 1, \"f\": 4}\n",
    "\n",
    "A, cache = pool_forward(A_prev, hparameters)\n",
    "print(\"mode = max\")\n",
    "print(\"A =\", A)\n",
    "print()\n",
    "A, cache = pool_forward(A_prev, hparameters, mode = \"average\")\n",
    "print(\"mode = average\")\n",
    "print(\"A =\", A)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# conv backward\n",
    "def conv_backward(dZ,cache):\n",
    "    \"\"\"\n",
    "    实现卷积层的反向传播\n",
    "\n",
    "    参数：\n",
    "        dZ - 卷积层的输出Z的 梯度，维度为(m, n_H, n_W, n_C)\n",
    "        cache - 反向传播所需要的参数，conv_forward()的输出之一\n",
    "\n",
    "    返回：\n",
    "        dA_prev - 卷积层的输入（A_prev）的梯度值，维度为(m, n_H_prev, n_W_prev, n_C_prev)\n",
    "        dW - 卷积层的权值的梯度，维度为(f,f,n_C_prev,n_C)\n",
    "        db - 卷积层的偏置的梯度，维度为（1,1,1,n_C）\n",
    "\n",
    "    \"\"\"\n",
    "    #获取cache的值\n",
    "    (A_prev, W, b, hparameters) = cache\n",
    "\n",
    "    #获取A_prev的基本信息\n",
    "    (m, n_H_prev, n_W_prev, n_C_prev) = A_prev.shape\n",
    "\n",
    "    #获取dZ的基本信息\n",
    "    (m,n_H,n_W,n_C) = dZ.shape\n",
    "\n",
    "    #获取权值的基本信息\n",
    "    (f, f, n_C_prev, n_C) = W.shape\n",
    "\n",
    "    #获取hparaeters的值\n",
    "    pad = hparameters[\"pad\"]\n",
    "    stride = hparameters[\"stride\"]\n",
    "\n",
    "    #初始化各个梯度的结构\n",
    "    dA_prev = np.zeros((m,n_H_prev,n_W_prev,n_C_prev))\n",
    "    dW = np.zeros((f,f,n_C_prev,n_C))\n",
    "    db = np.zeros((1,1,1,n_C))\n",
    "\n",
    "    #前向传播中我们使用了pad，反向传播也需要使用，这是为了保证数据结构一致\n",
    "    A_prev_pad = zero_pad(A_prev,pad)\n",
    "    dA_prev_pad = zero_pad(dA_prev,pad)\n",
    "\n",
    "    #现在处理数据\n",
    "    for i in range(m):\n",
    "        #选择第i个扩充了的数据的样本,降了一维。\n",
    "        a_prev_pad = A_prev_pad[i]\n",
    "        da_prev_pad = dA_prev_pad[i]\n",
    "\n",
    "        for h in range(n_H):\n",
    "            for w in range(n_W):\n",
    "                for c in range(n_C):\n",
    "                    #定位切片位置\n",
    "                    vert_start = h\n",
    "                    vert_end = vert_start + f\n",
    "                    horiz_start = w\n",
    "                    horiz_end = horiz_start + f\n",
    "\n",
    "                    #定位完毕，开始切片\n",
    "                    a_slice = a_prev_pad[vert_start:vert_end,horiz_start:horiz_end,:]\n",
    "\n",
    "                    #切片完毕，使用上面的公式计算梯度\n",
    "                    da_prev_pad[vert_start:vert_end, horiz_start:horiz_end,:] += W[:,:,:,c] * dZ[i, h, w, c]\n",
    "                    dW[:,:,:,c] += a_slice * dZ[i,h,w,c]\n",
    "                    db[:,:,:,c] += dZ[i,h,w,c]\n",
    "        #设置第i个样本最终的dA_prev,即把非填充的数据取出来。\n",
    "        dA_prev[i,:,:,:] = da_prev_pad[pad:-pad, pad:-pad, :]\n",
    "\n",
    "    #数据处理完毕，验证数据格式是否正确\n",
    "    assert(dA_prev.shape == (m, n_H_prev, n_W_prev, n_C_prev))\n",
    "\n",
    "    return (dA_prev,dW,db)\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#test\n",
    "np.random.seed(1)\n",
    "#初始化参数\n",
    "A_prev = np.random.randn(10,4,4,3)\n",
    "W = np.random.randn(2,2,3,8)\n",
    "b = np.random.randn(1,1,1,8)\n",
    "hparameters = {\"pad\" : 2, \"stride\": 1}\n",
    "\n",
    "#前向传播\n",
    "Z , cache_conv = conv_forward(A_prev,W,b,hparameters)\n",
    "#反向传播\n",
    "dA , dW , db = conv_backward(Z,cache_conv)\n",
    "print(\"dA_mean =\", np.mean(dA))\n",
    "print(\"dW_mean =\", np.mean(dW))\n",
    "print(\"db_mean =\", np.mean(db))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 最大池化的反向传播 pool backward\n",
    "# mask \n",
    "def create_mask_from_window(x):\n",
    "    \"\"\"\n",
    "    从输入矩阵中创建掩码，以保存最大值的矩阵的位置。\n",
    "\n",
    "    参数：\n",
    "        x - 一个维度为(f,f)的矩阵\n",
    "\n",
    "    返回：\n",
    "        mask - 包含x的最大值的位置的矩阵\n",
    "    \"\"\"\n",
    "    mask = x == np.max(x)\n",
    "\n",
    "    return mask\n",
    "#test mask\n",
    "np.random.seed(1)\n",
    "\n",
    "x = np.random.randn(2,3)\n",
    "\n",
    "mask = create_mask_from_window(x)\n",
    "\n",
    "print(\"x = \" + str(x)) \n",
    "print(\"mask = \" + str(mask))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# pool backward\n",
    "def distribute_value(dz,shape):\n",
    "    \"\"\"\n",
    "    给定一个值，为按矩阵大小平均分配到每一个矩阵位置中。\n",
    "\n",
    "    参数：\n",
    "        dz - 输入的实数\n",
    "        shape - 元组，两个值，分别为n_H , n_W\n",
    "\n",
    "    返回：\n",
    "        a - 已经分配好了值的矩阵，里面的值全部一样。\n",
    "\n",
    "    \"\"\"\n",
    "    #获取矩阵的大小\n",
    "    (n_H , n_W) = shape\n",
    "\n",
    "    #计算平均值\n",
    "    average = dz / (n_H * n_W)\n",
    "\n",
    "    #填充入矩阵\n",
    "    a = np.ones(shape) * average\n",
    "\n",
    "    return a\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# test\n",
    "dz = 2\n",
    "shape = (2,2)\n",
    "\n",
    "a = distribute_value(dz,shape)\n",
    "print(\"a = \" + str(a))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def pool_backward(dA,cache,mode = \"max\"):\n",
    "    \"\"\"\n",
    "    实现池化层的反向传播\n",
    "\n",
    "    参数:\n",
    "        dA - 池化层的输出的梯度，和池化层的输出的维度一样\n",
    "        cache - 池化层前向传播时所存储的参数。\n",
    "        mode - 模式选择，【\"max\" | \"average\"】\n",
    "\n",
    "    返回：\n",
    "        dA_prev - 池化层的输入的梯度，和A_prev的维度相同\n",
    "\n",
    "    \"\"\"\n",
    "    #获取cache中的值\n",
    "    (A_prev , hparaeters) = cache\n",
    "\n",
    "    #获取hparaeters的值\n",
    "    f = hparaeters[\"f\"]\n",
    "    stride = hparaeters[\"stride\"]\n",
    "\n",
    "    #获取A_prev和dA的基本信息\n",
    "    (m , n_H_prev , n_W_prev , n_C_prev) = A_prev.shape\n",
    "    (m , n_H , n_W , n_C) = dA.shape\n",
    "\n",
    "    #初始化输出的结构\n",
    "    dA_prev = np.zeros_like(A_prev)\n",
    "\n",
    "    #开始处理数据\n",
    "    for i in range(m):\n",
    "        a_prev = A_prev[i]      \n",
    "        for h in range(n_H):\n",
    "            for w in range(n_W):\n",
    "                for c in range(n_C):\n",
    "                    #定位切片位置\n",
    "                    vert_start = h\n",
    "                    vert_end = vert_start + f\n",
    "                    horiz_start = w\n",
    "                    horiz_end = horiz_start + f\n",
    "\n",
    "                    #选择反向传播的计算方式\n",
    "                    if mode == \"max\":\n",
    "                        #开始切片\n",
    "                        a_prev_slice = a_prev[vert_start:vert_end,horiz_start:horiz_end,c]\n",
    "                        #创建掩码\n",
    "                        mask = create_mask_from_window(a_prev_slice)\n",
    "                        #计算dA_prev\n",
    "                        dA_prev[i,vert_start:vert_end,horiz_start:horiz_end,c] += np.multiply(mask,dA[i,h,w,c])\n",
    "\n",
    "                    elif mode == \"average\":\n",
    "                        #获取dA的值\n",
    "                        da = dA[i,h,w,c]\n",
    "                        #定义过滤器大小\n",
    "                        shape = (f,f)\n",
    "                        #平均分配\n",
    "                        dA_prev[i,vert_start:vert_end, horiz_start:horiz_end ,c] += distribute_value(da,shape)\n",
    "    #数据处理完毕，开始验证格式\n",
    "    assert(dA_prev.shape == A_prev.shape)\n",
    "\n",
    "    return dA_prev"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# test\n",
    "np.random.seed(1)\n",
    "A_prev = np.random.randn(5, 5, 3, 2)\n",
    "hparameters = {\"stride\" : 1, \"f\": 2}\n",
    "A, cache = pool_forward(A_prev, hparameters)\n",
    "dA = np.random.randn(5, 4, 2, 2)\n",
    "\n",
    "dA_prev = pool_backward(dA, cache, mode = \"max\")\n",
    "print(\"mode = max\")\n",
    "print('mean of dA = ', np.mean(dA))\n",
    "print('dA_prev[1,1] = ', dA_prev[1,1])  \n",
    "print()\n",
    "dA_prev = pool_backward(dA, cache, mode = \"average\")\n",
    "print(\"mode = average\")\n",
    "print('mean of dA = ', np.mean(dA))\n",
    "print('dA_prev[1,1] = ', dA_prev[1,1]) \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
